/**
 * FileFind - ftp/shares indexing and search php engine
 * Module: agent-java
 * Description: Agent written in java language
 * Copyright (c) 2003-2010 Rage Steel, www.gt2.ru
 */
package ru.ffind.agent;

import java.io.File;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.DriverManagerDataSource;
import ru.ffind.agent.dao.Dao;
import ru.ffind.agent.dao.DaoImpl;

import ru.ffind.agent.db.DbListingStorage;
import ru.ffind.agent.db.DbScanner;
import ru.ffind.agent.db.DbSyncActions;
import ru.ffind.agent.io.FileListingStorage;
import ru.ffind.agent.io.FileScanner;
import ru.ffind.agent.sync.Sync;
import ru.ffind.agent.sync.SyncActions;

/**
 * Runner class for java agent
 *
 * @author Rage Steel
 */
public class AgentRunner {

	private final static Logger LOGGER = 
		Logger.getLogger(AgentRunner.class.getName()); 
	
	private AgentRunner() {
        // Private constuctor to disallow creating of instance
	}
	
	private static AgentConfig config = AgentConfigImpl.INSTANCE;

	public static void main(String[] args) {
		oneByOne();
	}

	private static DataSource getDataSource() {
		DriverManagerDataSource dataSource = new DriverManagerDataSource(config.getDbUrl(), 
			config.getDbUsername(), config.getDbPassword());
		dataSource.setDriverClassName(config.getDbDriverClassname());
		return dataSource;
	}

    private static Dao getDao(DataSource dataSource) {
        return new DaoImpl(dataSource);
    }
	
	/**
	 * Execute agent tasks one by one
	 */
	public static void oneByOne() {
		DataSource dataSource = getDataSource();
        Dao dao = getDao(dataSource);
		DbScanner dbScanner = new DbScanner(dao, config);
		if (!dbScanner.isReady()) {
			LOGGER.log(Level.INFO, "Host is not ready for indexing, exiting");
			return;
		}

		DbListingStorage dbListingStorage = new DbListingStorage();
		LOGGER.log(Level.INFO, "Reading directory structure in the database...");
		dbScanner.scan(dbListingStorage);
		dbListingStorage.setScanComplete();
		LOGGER.log(Level.INFO, "  complete, files count: {0}",
				dbListingStorage.getFilesCount());
		
		File[] roots = config.getRoots();
		FileListingStorage filesListingStorage = new FileListingStorage(roots);

		LOGGER.log(Level.INFO, "Scanning filesystems...");
		for (File root: roots) {
			FileScanner fileScanner = new FileScanner(root);
			fileScanner.scan(filesListingStorage);
			filesListingStorage.setFileScanComplete(root);
		}
		LOGGER.log(Level.INFO, "  complete, files count: {0}",
                filesListingStorage.getFilesCount());
		
		LOGGER.log(Level.INFO, "Syncronizing...");
		SyncActions syncActions = new DbSyncActions(filesListingStorage, dbListingStorage, dao, dbScanner.getHost());
		Sync sync = new Sync(filesListingStorage, dbListingStorage, syncActions);		
		sync.sync();
		
		LOGGER.log(Level.INFO, "Done!");
	}
	
	private static final UncaughtExceptionHandler PRINT_STACK_TRACE_HANDLER = new UncaughtExceptionHandler() {
		
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			LOGGER.log(Level.WARNING, "Uncaught exception in thread", e);
		}
	};	
	
	/**
	 * Execute agent task in parallel
	 */
	public static void inSync() {
//		System.out.println(	Arrays.toString(File.listRoots()));
//		System.out.println(Arrays.toString(FileSystemView.getFileSystemView().getRoots()));

		// Preparing database reader thread
		DataSource dataSource = getDataSource();
        Dao dao = getDao(dataSource);
		
		final DbScanner dbScanner = new DbScanner(dao, config);
		if (!dbScanner.isReady()) {
			LOGGER.log(Level.INFO, "Host is not ready for indexing, exiting");
			return;
		}
		
		File[] roots = config.getRoots(); 
		final FileListingStorage filesListingStorage = new FileListingStorage(roots);
		final DbListingStorage dbListingStorage = new DbListingStorage();
		
		// One thread for database reading
		// another for database sync
		// and one for scanning file system
		ExecutorService executor = Executors.newFixedThreadPool(3);
		
		// Database reading thread
		executor.submit(new Runnable () {
			
			@Override
			public void run() {
				Thread.setDefaultUncaughtExceptionHandler(PRINT_STACK_TRACE_HANDLER);
				dbScanner.scan(dbListingStorage);
				dbListingStorage.setScanComplete();
			}
			
		});
		
		// Database sync thread
		SyncActions syncActions = new DbSyncActions(filesListingStorage, dbListingStorage, dao, dbScanner.getHost());
		final Sync sync = new Sync(filesListingStorage, dbListingStorage, syncActions);
//		executor.submit(new Runnable() {
//			
//			@Override
//			public void run() {
//				Thread.setDefaultUncaughtExceptionHandler(printStackTrace);
//				sync.sync();
//			}
//		});
		
		// Start file scanning threads one by one
		for (final File root: roots) {
			
			Future<?> future = executor.submit(new Runnable() {
				
				@Override
				public void run() {
					Thread.setDefaultUncaughtExceptionHandler(PRINT_STACK_TRACE_HANDLER);
					FileScanner fileScanner = new FileScanner(root);
					fileScanner.scan(filesListingStorage);
				}
				
			});
			
			// wait for result
			try {
				future.get();
			} catch (Exception e) {
				LOGGER.log(Level.WARNING, "Exception in thread", e);
			}
			LOGGER.log(Level.INFO, "Scan complete, total files/directories: {0}",
					filesListingStorage.getFilesCount());
			filesListingStorage.setFileScanComplete(root);
		}
		
		executor.shutdown();
		
		sync.sync();
		
//		filesListingStorage.dump();
	}

}
